Write a custom CUDA kernel to optimize `torch.nn.LPPool1d`.

The operation performs 1D LP-norm pooling over an input signal. For each sliding window, it computes `(sum(x^p))^(1/p)`, where `p` is the `norm_power`.

**Problem Analysis:**
The standard PyTorch implementation of pooling layers often relies on an `unfold` (or `im2col`) operation to extract the sliding windows. This approach has significant performance drawbacks:
1.  **Memory Explosion**: The `unfold` operation creates a massive intermediate tensor containing all extracted windows. For a 1D signal, this can increase memory usage by a factor of `kernel_size`, becoming a major memory bandwidth bottleneck.
2.  **Multiple Kernel Launches**: After unfolding, a sequence of separate element-wise and reduction kernels are launched (`pow`, `sum`, `pow`), each requiring a full pass over the data and incurring kernel launch latency.

**Optimization Strategy: Fused Output-Oriented Kernel**

The optimization strategy is to create a single, fused CUDA kernel that computes the pooling result directly, avoiding the `unfold` operation entirely.

1.  **Output-Oriented Parallelism**: The kernel is launched with one thread for each element of the **output tensor**. Each thread is uniquely responsible for computing one final output value.

2.  **Direct Window Computation**: Each thread first calculates its position `(n, c, l_out)` in the output tensor. From this, it computes the corresponding window's start and end indices in the input tensor based on `stride` and `kernel_size`.

3.  **In-Register Reduction**: The thread then loops over the elements of its assigned input window. The entire LP-norm calculation (`pow(x, p)`, summation) is performed within the thread's private registers. This is extremely fast and completely avoids writing any intermediate data to global memory.

4.  **Full Fusion**: After the loop, the final `pow(1/p)` operation is applied, and the thread writes the single, final result to its designated position in the output tensor. This approach fuses the `unfold`, `pow`, `sum`, and final `pow` operations into a single memory pass, dramatically improving performance by minimizing memory traffic and kernel launch overhead. It also benefits from good data locality, as adjacent threads access overlapping regions of the input tensor, leading to efficient cache utilization.
  
Here's an example to show you the syntax of inline embedding custom CUDA operators in torch: The example given architecture is:   
  
```python
import torch
import torch.nn as nn
import torch.nn.functional as F


class Model(nn.Module):
    def __init__(self) -> None:
        super().__init__()

    def forward(self, a, b):
        return a + b


def get_inputs():
    # randomly generate input tensors based on the model architecture
    a = torch.randn(1, 128).cuda()
    b = torch.randn(1, 128).cuda()
    return [a, b]


def get_init_inputs():
    # randomly generate tensors required for initialization based on the model architecture
    return []
```
  
The example new arch with custom CUDA kernels looks like this:   
```python
import torch
import torch.nn as nn

BATCH_SIZE = 512
CHANNELS = 256
L_IN = 1024
NORM_POWER = 2
KERNEL_SIZE = 3
STRIDE = 1

class Model(nn.Module):
    def __init__(self, norm_power, kernel_size, stride):
        super(Model, self).__init__()
        self.pool = nn.LPPool1d(norm_type=norm_power, kernel_size=kernel_size, stride=stride)
    
    def forward(self, input_tensor: torch.Tensor) -> torch.Tensor:
        return self.pool(input_tensor)

def get_inputs():
    """
    生成用于测试的输入张量。
    """
    # LPPool 对负值敏感，使用正值以保证与 powf 的行为一致
    input_tensor = torch.rand(BATCH_SIZE, CHANNELS, L_IN, dtype=torch.float32) + 0.1
    return [input_tensor.contiguous()]

def get_init_inputs():
    return [NORM_POWER, KERNEL_SIZE, STRIDE]
```